# Create subplots
fig = make_subplots(
rows=1,
cols=2,
subplot_titles=("Out-Degree Power Law", "In-Degree Power Law"),
)
# Out-degree power law
if "out_degree_fit" in power_law_fits:
fit = power_law_fits["out_degree_fit"]
# Calculate degree distribution from samples
out_degrees = np.array(distributions["out_degrees"])
unique_out, counts_out = np.unique(out_degrees[out_degrees > 0], return_counts=True)
# Limit points for visualization
if len(unique_out) > 100:
indices = np.linspace(0, len(unique_out) - 1, 100, dtype=int)
unique_out = unique_out[indices]
counts_out = counts_out[indices]
# Scatter plot of actual data
fig.add_trace(
go.Scatter(
x=unique_out,
y=counts_out,
mode="markers",
name="Actual",
marker=dict(color="lightseagreen", size=8, opacity=0.5),
),
row=1,
col=1,
)
# Fitted line using the fit parameters
x_fit = np.logspace(np.log10(fit["fit_range"][0]), np.log10(fit["fit_range"][1]), 100)
y_fit = 10 ** (fit["intercept"]) * x_fit ** (-fit["alpha"])
fig.add_trace(
go.Scatter(
x=x_fit,
y=y_fit,
mode="lines",
name=f"Fit (α={fit['alpha']:.3f})",
line=dict(color="darksalmon", width=3, dash="dash"),
),
row=1,
col=1,
)
# Add annotations
annotation_text = f"R² = {fit['r_squared']:.4f}<br>"
annotation_text += f"α = {fit['alpha']:.4f}"
fig.add_annotation(
text=annotation_text,
xref="x domain",
yref="y domain",
x=0.3,
y=0.98,
bgcolor="#fafafa",
showarrow=False,
xanchor="right",
yanchor="top",
)
# In-degree power law
if "in_degree_fit" in power_law_fits:
fit = power_law_fits["in_degree_fit"]
# Calculate degree distribution from samples
in_degrees = np.array(distributions["in_degrees"])
unique_in, counts_in = np.unique(in_degrees[in_degrees > 0], return_counts=True)
# Limit points for visualization
if len(unique_in) > 100:
indices = np.linspace(0, len(unique_in) - 1, 100, dtype=int)
unique_in = unique_in[indices]
counts_in = counts_in[indices]
# Scatter plot
fig.add_trace(
go.Scatter(
x=unique_in,
y=counts_in,
mode="markers",
name="Actual",
marker=dict(color="plum", size=8, opacity=0.5),
showlegend=False,
),
row=1,
col=2,
)
# Fitted line using the fit parameters
x_fit = np.logspace(np.log10(fit["fit_range"][0]), np.log10(fit["fit_range"][1]), 100)
y_fit = 10 ** (fit["intercept"]) * x_fit ** (-fit["alpha"])
fig.add_trace(
go.Scatter(
x=x_fit,
y=y_fit,
mode="lines",
name=f"Fit (α={fit['alpha']:.3f})",
line=dict(color="darksalmon", width=3, dash="dash"),
showlegend=False,
),
row=1,
col=2,
)
# Add annotations
annotation_text = f"R² = {fit['r_squared']:.4f}<br>"
annotation_text += f"α = {fit['alpha']:.4f}"
fig.add_annotation(
text=annotation_text,
xref="x2 domain",
yref="y2 domain",
x=0.3,
y=0.98,
showarrow=False,
bgcolor="#fafafa",
xanchor="right",
yanchor="top",
)
# Update axes to log scale
fig.update_xaxes(type="log", title="Degree (k)", row=1, col=1)
fig.update_xaxes(type="log", title="Degree (k)", row=1, col=2)
fig.update_yaxes(type="log", title="Frequency P(k)", row=1, col=1)
fig.update_yaxes(type="log", title="Frequency P(k)", row=1, col=2)
fig.update_layout(height=500, showlegend=False, margin=dict(t=40))
fig.show()